1 module hip.game2d.ninepatch;
2 public import hip.api.renderer.texture;
3 public import hip.game2d.sprite;
4 import hip.api.renderer.shaders.spritebatch;
5 import hip.api.data.textureatlas;
6 
7 enum NinePatchType
8 {
9     SCALED,
10     TILED //I Think this effect is quite ugly, but maybe it'll be useful at some time
11 }
12 
13 class NinePatch
14 {
15     uint width, height;
16     float x = 0, y = 0;
17     float scaleX = 1, scaleY = 1;
18      HipSprite[9] sprites;
19     protected HipSpriteVertex[9*4] vertices;
20     IHipTexture texture;
21     NinePatchType stretchStrategy;
22 
23     this(IHipTexture tex, NinePatchType t = NinePatchType.SCALED)
24     {
25         texture = tex;
26         foreach(i; 0..9)
27         {
28             sprites[i] = new HipSprite(tex);
29             sprites[i].setOrigin(0,0);
30         }
31         stretchStrategy = t;
32     }
33 
34     this(uint width, uint height, IHipTexture tex, NinePatchType type = NinePatchType.SCALED)
35     {
36         this.width = width;
37         this.height = height;
38         texture = tex;
39         stretchStrategy = type;
40         for(int i = 0; i < 9; i++)
41         {
42             sprites[i] = new HipSprite(tex);
43             sprites[i].setOrigin(0,0);
44         }
45 
46         setTextureRegions();
47         setSize(width, height);
48     }
49 
50     void setSize(int width, int height)
51     {
52         this.width = width;
53         this.height = height;
54         build();
55     }
56 
57     /**
58     *   The arguments will be divided by the texture width and height for
59     *   generating the UVs
60     */
61     void setTextureRegions(uint x, uint y, uint width, uint height)
62     {
63         int texWidth = sprites[0].getTextureWidth;
64         int texHeight = sprites[0].getTextureHeight;
65 
66         float tx = cast(float)x/texWidth;
67         float ty = cast(float)y/texHeight;
68 
69         float xw = (cast(float)width/3.0f)/texWidth;
70         float yh = (cast(float)height/3.0f)/texHeight;
71         float xw2 = xw*2;
72         float xw3 = xw*3;
73         float yh2 = yh*2;
74         float yh3 = yh*3;
75 
76         sprites[TOP_LEFT].setRegion (tx+0,   ty+0, tx+xw,  ty+yh);
77         sprites[TOP_MID].setRegion  (tx+xw,  ty+0, tx+xw2, ty+yh);
78         sprites[TOP_RIGHT].setRegion(tx+xw2, ty+0, tx+xw3, ty+yh);
79 
80         sprites[MID_LEFT].setRegion (tx+0,  ty+yh, tx+xw,  ty+yh2);
81         sprites[MID_MID].setRegion  (tx+xw, ty+ yh,tx+ xw2,ty+ yh2);
82         sprites[MID_RIGHT].setRegion(tx+xw2,ty+ yh,tx+ xw3,ty+ yh2);
83 
84         sprites[BOT_LEFT].setRegion (tx+0,  ty+yh2, tx+xw,  ty+yh3);
85         sprites[BOT_MID].setRegion  (tx+xw, ty+ yh2,tx+ xw2,ty+ yh3);
86         sprites[BOT_RIGHT].setRegion(tx+xw2,ty+ yh2,tx+ xw3,ty+ yh3);
87     }
88 
89     /**
90     *   Cuts the entire image in 9 slices
91     */
92     void setTextureRegions()
93     {
94         setTextureRegions(0, 0, sprites[0].getTextureWidth, sprites[0].getTextureHeight);
95     }
96 
97 
98     void build()
99     {
100         static float getXScalingFactor(ubyte index, HipSprite[] sprites, uint width)
101         {
102             float ret = (width - (sprites[index-1].width + sprites[index+1].width));
103             if(sprites[index].width != 0)
104                 ret/= cast(float)sprites[index].width;
105             return ret;
106         }
107 
108         static float getYScalingFactor(ubyte index, HipSprite[] sprites, uint height)
109         {
110             float ret = (height - (sprites[index-3].height + sprites[index+3].height));
111             if(sprites[index].height != 0)
112                 ret/= cast(float)sprites[index].height;
113             return ret;
114         }
115         /**
116          * Receives the _LEFT part to calculate the scale.
117          * This scale is used whenever there is a need of scale being less than 1
118          * Params:
119          *   index =
120          *   sprites =
121          *   width =
122          * Returns:
123          */
124         static float getSingleXScaling(ubyte index, HipSprite[] sprites, uint width)
125         {
126             if(width < sprites[index].width + sprites[index+2].width)
127                 return cast(float)width / (sprites[index].width + sprites[index+2].width);
128             return 1;
129         }
130         static float getSingleYScaling(ubyte index, HipSprite[] sprites, uint height)
131         {
132             if(height < sprites[index].height + sprites[index+6].height)
133                 return cast(float)height / (sprites[index].height + sprites[index+6].height);
134             return 1;
135         }
136 
137         float scaleXTop = getSingleXScaling(TOP_LEFT, sprites, width);
138         float scaleYTop = getSingleYScaling(TOP_LEFT, sprites, height);
139 
140 
141         int spWidth = sprites[TOP_LEFT].width;
142         int spHeight = sprites[TOP_LEFT].height;
143 
144         int px2 = cast(int)(width-spWidth*scaleXTop);
145         int py2 = cast(int)(height-spHeight*scaleYTop);
146 
147 
148         //First, take care of those which don't scale.
149         sprites[TOP_LEFT].setPosition(x, y);
150         sprites[TOP_RIGHT].setPosition(x + px2, y);
151         sprites[BOT_LEFT].setPosition(x, y + py2);
152         sprites[BOT_RIGHT].setPosition(x + px2, y + py2);
153 
154         ///Those scales may only change whenever they are actually smaller than a scalable size
155         sprites[TOP_LEFT].setScale(scaleXTop, scaleYTop);
156         sprites[TOP_RIGHT].setScale(scaleXTop, scaleYTop);
157         sprites[BOT_LEFT].setScale(scaleXTop, scaleYTop);
158         sprites[BOT_RIGHT].setScale(scaleXTop, scaleYTop);
159 
160         //Now, those which scales in only one direction
161         sprites[TOP_MID].setPosition(x+spWidth, y);
162         sprites[MID_LEFT].setPosition(x, y+spHeight);
163         sprites[MID_RIGHT].setPosition(x+ px2, y+spHeight);
164         sprites[BOT_MID].setPosition(x+spWidth, y + py2);
165         sprites[MID_MID].setPosition(spWidth+x, spHeight+y);
166 
167 
168 
169         if(stretchStrategy == NinePatchType.SCALED)
170         {
171             sprites[TOP_MID].setScale(getXScalingFactor(TOP_MID ,sprites, width), scaleYTop);
172             sprites[MID_LEFT].setScale(scaleXTop, getYScalingFactor(MID_LEFT, sprites, height));
173             sprites[MID_RIGHT].setScale(scaleXTop, getYScalingFactor(MID_RIGHT, sprites, height));
174             sprites[BOT_MID].setScale(getXScalingFactor(BOT_MID, sprites, width), scaleYTop);
175 
176             //The last one
177             sprites[MID_MID].setScale(getXScalingFactor(MID_MID, sprites, width),  getYScalingFactor(MID_MID, sprites, height));
178         }
179         else
180         {
181             sprites[TOP_MID].setTiling(getXScalingFactor(TOP_MID ,sprites, width), scaleYTop);
182             sprites[MID_LEFT].setTiling(scaleXTop, getYScalingFactor(MID_LEFT, sprites, height));
183             sprites[MID_RIGHT].setTiling(scaleXTop, getYScalingFactor(MID_RIGHT, sprites, height));
184             sprites[BOT_MID].setTiling(getXScalingFactor(BOT_MID, sprites, width), scaleYTop);
185 
186             //The last one
187             sprites[MID_MID].setTiling(getXScalingFactor(MID_MID, sprites, width),  getYScalingFactor(MID_MID, sprites, height));
188         }
189 
190         // uint thresholdWidth = spWidth*2;
191         // uint thresholdHeight = spHeight*2;
192         
193         // if(width < thresholdWidth)
194         // {
195         //     float sX = (width/2.0)/thresholdWidth;
196         //     sprites[TOP_LEFT].setScale(sX, sprites[TOP_LEFT].scaleY);
197         //     sprites[TOP_RIGHT].setScale(sX, sprites[TOP_RIGHT].scaleY);
198 
199         //     sprites[MID_LEFT].setScale(sX, sprites[MID_LEFT].scaleY);
200         //     sprites[MID_RIGHT].setScale(sX, sprites[MID_RIGHT].scaleY);
201 
202         //     sprites[BOT_LEFT].setScale(sX, sprites[BOT_LEFT].scaleY);
203         //     sprites[BOT_RIGHT].setScale(sX, sprites[BOT_RIGHT].scaleY);
204 
205         //     sprites[TOP_MID].setScale(0,0);
206         //     sprites[MID_MID].setScale(0,0);
207         //     sprites[BOT_MID].setScale(0,0);
208         // }
209         // if(height < thresholdHeight)
210         // {
211         //     float sY = (height/2.0)/thresholdHeight;
212         //     sprites[TOP_LEFT].setScale(sprites[TOP_LEFT].scaleX, sY);
213         //     sprites[TOP_RIGHT].setScale(sprites[TOP_RIGHT].scaleX, sY);
214 
215         //     sprites[MID_LEFT].setScale(sprites[MID_LEFT].scaleX, sY);
216         //     sprites[MID_RIGHT].setScale(sprites[MID_RIGHT].scaleX, sY);
217 
218         //     sprites[BOT_LEFT].setScale(sprites[BOT_LEFT].scaleX, sY);
219         //     sprites[BOT_RIGHT].setScale(sprites[BOT_RIGHT].scaleX, sY);
220 
221         //     sprites[TOP_MID].setScale(0,0);
222         //     sprites[MID_MID].setScale(0,0);
223         //     sprites[BOT_MID].setScale(0,0);
224         // }
225 
226         vertices[0..4] = sprites[TOP_LEFT].getVertices();
227         vertices[4..8] = sprites[TOP_MID].getVertices();
228         vertices[8..12] = sprites[TOP_RIGHT].getVertices();
229         vertices[12..16] = sprites[MID_LEFT].getVertices();
230         vertices[16..20] = sprites[MID_MID].getVertices();
231         vertices[20..24] = sprites[MID_RIGHT].getVertices();
232         vertices[24..28] = sprites[BOT_LEFT].getVertices();
233         vertices[28..32] = sprites[BOT_MID].getVertices();
234         vertices[32..36] = sprites[BOT_RIGHT].getVertices();
235     }
236 
237     void setTopLeft(TextureCoordinatesQuad q){sprites[TOP_LEFT].setRegion(q);}
238     void setTopMid(TextureCoordinatesQuad q){sprites[TOP_MID].setRegion(q);}
239     void setTopRight(TextureCoordinatesQuad q){sprites[TOP_RIGHT].setRegion(q);}
240     
241 
242     void setMidLeft (TextureCoordinatesQuad q){sprites[MID_LEFT].setRegion(q);}
243     void setMidMid  (TextureCoordinatesQuad q){sprites[MID_MID].setRegion(q);}
244     void setMidRight(TextureCoordinatesQuad q){sprites[MID_RIGHT].setRegion(q);}
245 
246     void setBotLeft (TextureCoordinatesQuad q){sprites[BOT_LEFT].setRegion(q);}
247     void setBotMid  (TextureCoordinatesQuad q){sprites[BOT_MID].setRegion(q);}
248     void setBotRight(TextureCoordinatesQuad q){sprites[BOT_RIGHT].setRegion(q);}
249 
250 
251     void setPosition(float x, float y)
252     {
253         this.x = x;
254         this.y = y;
255         updatePosition();
256     }
257 
258 
259    void setColor(HipColor color)
260    {
261        int quad = 0;
262        for(int i = 0; i < 9; i++)
263        {
264             quad = cast(int)(i*4);
265             vertices[quad].vColor = color;
266             vertices[quad+1].vColor = color;
267             vertices[quad+2].vColor = color;
268             vertices[quad+3].vColor = color;
269         }
270    }
271    public ref HipSpriteVertex[4*9] getVertices(){return vertices;}
272 
273 
274     /**
275     *   Use this function instead of build for less overhead
276     */
277     protected void updatePosition()
278     {
279         uint spWidth = sprites[TOP_LEFT].width;
280         uint spHeight = sprites[TOP_LEFT].height;
281         sprites[TOP_LEFT].setPosition(x, y);
282         sprites[TOP_RIGHT].setPosition(x + (width-spWidth), y);
283         sprites[BOT_LEFT].setPosition(x, y + (height-spHeight));
284         sprites[BOT_RIGHT].setPosition(x + (width-spWidth), y + (height-spHeight));
285         sprites[TOP_MID].setPosition(x+spWidth, y);
286         sprites[MID_LEFT].setPosition(x, y+spHeight);
287         sprites[MID_RIGHT].setPosition(x+width-spWidth, y+spHeight);
288         sprites[BOT_MID].setPosition(x+spWidth, y + (height-spHeight));
289         sprites[MID_MID].setPosition(spWidth+x, spHeight+y);
290 
291         for(uint i = 0; i < 9; i++)
292         {
293             uint quad = i*4;
294             HipSpriteVertex[] verts = sprites[i].getVertices();
295             vertices[quad].vPosition = verts[0].vPosition;
296             vertices[quad+1].vPosition = verts[1].vPosition;
297             vertices[quad+2].vPosition = verts[2].vPosition;
298             vertices[quad+3].vPosition = verts[3].vPosition;
299         }
300     }
301 
302     void draw()
303     {
304         foreach(HipSprite sp; sprites)
305             sp.draw;
306     }
307     /**
308      Creates a NinePatch from the matrix, starting from left to right, top to bottom
309      * Params:
310      *   rects = The rects that defines the sprite regions
311      *   tex = The texture being used as a reference to the regions
312      *   width = The width of the resulting nine patch
313      *   height = The height of the resulting nine patch
314      *   t = The scaling type
315      * Returns:
316      */
317     static NinePatch fromQuads(AtlasRect[9] rects, IHipTexture tex, int width, int height, NinePatchType t = NinePatchType.SCALED)
318     {
319         NinePatch ret = new NinePatch(tex, t);
320 
321         AtlasSize sz = AtlasSize(tex.getWidth, tex.getHeight);
322         foreach(i, sp; ret.sprites)
323         {
324             // rects[i].y = sz.height - rects[i].y;
325             sp.width  = cast(uint)rects[i].width;
326             sp.height = cast(uint)rects[i].height;
327             sp.setRegion(rects[i].toQuad(sz));
328         }
329 
330         ret.setSize(width, height);
331         return ret;
332     }
333 
334 
335 }
336 
337 private enum : ubyte
338 {
339     TOP_LEFT = 0,
340     TOP_MID,
341     TOP_RIGHT,
342 
343     MID_LEFT,
344     MID_MID,
345     MID_RIGHT,
346 
347     BOT_LEFT,
348     BOT_MID,
349     BOT_RIGHT
350 }